package org.gc.amino.universe;

import java.awt.geom.Point2D;
import java.util.ArrayList;

import org.gc.amino.ia.scalar.ScalarField;

public class MetaUniverse {
	private static final int CACHE_SIZE = 64;
	
	private float timestep;
	private Universe root_universe;
	private Point2D.Float[] possible_directions;
	
	private MetaUniverseTreeNode root_node;
	private MetaUniverseTreeNode current_node;
	
	private MetaUniverseCache cache;
	
	public MetaUniverse(Universe root, float timestep, Point2D.Float[] possible_directions) {
		this.timestep = timestep;
		
		this.root_universe = root;
		this.possible_directions = possible_directions;
		
		this.root_node = new MetaUniverseTreeNode(possible_directions.length, 0);
		this.current_node = this.root_node;
		
		this.cache = new MetaUniverseCache(CACHE_SIZE);
	}
	
	public MetaUniverse(Universe root, float timestep, int number_of_possible_directions) {
		this.timestep = timestep;
		
		this.root_universe = root;
		this.possible_directions = new Point2D.Float[number_of_possible_directions];
		
		// Filling up possible directions
		for(int i = 0; i < number_of_possible_directions; ++i) {
			double angle = 2 * Math.PI / i;
			this.possible_directions[i] = 
					new Point2D.Float((float)Math.cos(angle), (float)Math.sin(angle));
		}
		
		this.root_node = new MetaUniverseTreeNode(number_of_possible_directions, 0);
		this.current_node = this.root_node;
		
		this.cache = new MetaUniverseCache(CACHE_SIZE);
	}
	
	public Universe getCurrentUniverse() {
		return this.current_node.makeUniverse(this.possible_directions, this.timestep);
	}
	
	public Universe getRootUniverse() {
		return this.root_universe;
	}
	
	public Universe playout(int courage) {
		Universe universe = null;
		MetaUniverseTreeNode node = this.current_node;
		
		if(node.hasChild(courage)) {
			node = node.getChild(courage);
			
			// Attempting to find universe in cache
			universe = this.lookInCache(node);
			
		} else {
			node = node.makeChild(courage);
		}
		
		// If the universe wasn't found in the cache
		if(universe == null) {
			universe = node.makeUniverse(this.possible_directions, timestep);
			// Promote the node
			this.cache.push(node);
		}
		
		this.current_node = node;
		
		return universe;
	}
	
	public Universe playoutWithoutCache(int move) {
		Universe universe = null;
		MetaUniverseTreeNode node = this.current_node;
		
		if(node.hasChild(move)) {
			node = node.getChild(move);
		} else {
			node = node.makeChild(move);
		}
		
		universe = node.makeUniverse(this.possible_directions, timestep);
		// Promote the node
		this.cache.push(node);
		
		
		this.current_node = node;
		
		return universe;
	}
	
	public void rollback() {
		MetaUniverseTreeNode node = this.current_node.getParent();
		
		if(node != null) {
			this.current_node = node;
		}
	}
	
	/** Returns the corresponding universe if found, null if not */
	private Universe lookInCache(MetaUniverseTreeNode node) {
		Universe universe = null;
		
		if(this.cache.has(node)) {
			universe = node.getUniverse();
			// Increases the priority of node in the cache
			this.cache.promote(node);
		}
		
		return universe;
	}

	
	
	private ArrayList<Integer> bestMoves = new ArrayList<Integer>();
		
	
	public boolean gameOver() {
		return root_universe.getMe().isDead()||current_node.getDepth() ==50;
	}
	
	
	public double score(){
		return current_node.getUniverse().getMe().getRadius()/root_universe.getMe().getRadius();
	}
	
	
	public void findMoves(int[] courageValues){
		bestMoves.clear();
		int tmp_move;
		Universe universe = getCurrentUniverse();
		universe.getMe();
		for(int i=0; i< courageValues.length; i++){
			tmp_move = ScalarField.findBestDirection(universe, courageValues[i], universe.getMe());
			if (!bestMoves.contains(tmp_move))
				bestMoves.add(tmp_move);
		}
		
	}
	
	public int nbMoves(){
		return bestMoves.size();
	}
	
	public int getMove(int i){
		return bestMoves.get(i);
	}
	
	
	
	
	
}
